﻿using Jacobi.Vst.Framework;
using Jacobi.Vst.Core;
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using Aleator.Resources;

namespace Aleator.Core
{
    /// <summary>
    /// Phrase class for lead melodies
    /// </summary>
    public class MelodyPhrase : ConcretePhrase
    {
        #region Members

        private int m_melodyOctave = Session.HomeOctave.Value;
        private int m_baselength = 0;
        private int m_largeClusterLength = 0;
        private int m_oldClusterID = 0;
        private int m_previous = 0;

        private int[] m_clusterBeats;
        private int[] m_largeClusterBeats;
        private int[] m_oldClusterBeats;

        private List<MelodyCluster> m_clusters;

        #endregion

        #region Constructors

        /// <summary>
        /// Main constructor
        /// </summary>
        /// <param name="progression">Progression that phrase is being attached to</param>
        /// <param name="melodyphrase">Phrase to use in populating this phrase's note list(s)</param>
        public MelodyPhrase(ConcreteProgression progression, MelodyPhrase melodyphrase = null)
        {
            m_progression = progression;
            Notes = new List<ConcreteNote>();
            m_homeOctaveNotes = new List<ConcreteNote>();
            m_clusters = new List<MelodyCluster>();

            if (melodyphrase == null)
            {
                m_noteDistribution = (Distribution)Enum.ToObject(typeof(Distribution), Session.RndGenerator.Next(2));

                //TODO: Implement Melody multipliers
                foreach (Melody m in progression.Melodies)
                    BuildMelody(m);

                SetTotals();
            }
            else
            {
                m_noteDistribution = melodyphrase.m_noteDistribution;
                CopyToPhrase((ConcretePhrase)melodyphrase);
            }

            Progression.Notes.AddRange(m_notes);
        }

        #endregion

        #region Methods

        /// <summary>
        /// Determine how many notes are in the phrase
        /// </summary>
        /// <param name="sigma">Determines the shape of the bell curve when using normal distribution</param>
        protected override void SetTotals(double sigma = 2.0)
        {
            //Always use continuous for rigid. Allow for a few missing notes (one per measure)
            m_continuous.Beta = Progression.TotalBeats * 2;
            m_continuous.Alpha = Progression.TotalBeats * .5;

            m_noteTotal = (int)m_continuous.NextDouble();

            m_noteOnMessageNumber = Convert.ToInt16(AppResources.LeadOnMessageID);
            m_noteOffMessageNumber = Convert.ToInt16(AppResources.LeadOffMessageID);

            Generate(2);
        }

        /// <summary>
        /// Generate not implemented in base class yet
        /// </summary>
        /// <param name="resolution"></param>
        protected override void Generate(int resolution)
        {
            m_resolution = resolution;

            int max = m_progression.Numerator / m_baselength;

            int notespercluster = 0;
            int notesperlargecluster = 0;

            int beat = 0;
            int i = 0;

            if (m_baselength > 0)
            {
                notespercluster = (int)(((double)m_baselength / (double)m_progression.TotalBeats) * m_noteTotal);
                m_clusterBeats = new int[notespercluster];

                while (i < notespercluster)
                {
                    beat = Session.RndGenerator.Next(m_baselength * m_resolution);

                    if (!m_clusterBeats.Contains(beat))
                        m_clusterBeats[i++] = beat;
                }
            }

            i = 0;

            if (m_largeClusterLength > 0)
            {
                notesperlargecluster = (int)(((double)m_largeClusterLength / (double)m_progression.TotalBeats) * m_noteTotal);
                m_largeClusterBeats = new int [notesperlargecluster];

                while (i < notesperlargecluster)
                {
                    //TODO: Fix T
                    beat = Session.RndGenerator.Next(m_largeClusterLength * m_resolution);

                    if (!m_largeClusterBeats.Contains(beat))
                        m_largeClusterBeats[i++] = beat;
                }
            }

            foreach (MelodyCluster mc in m_clusters)
            {
                FillCluster(mc);
                m_previous = mc.Location + mc.Beats;
            }

            i = m_progression.TotalMeasures - 1;

            Multiply();
            m_notes.RemoveAll(n => n == null);

            AddRests();

            //TODO: Come up with a more elegant solution for hanging notes.
            //this.Notes.RemoveAll(n => n.OffMeasure > m_progression.TotalMeasures - 1);
            //this.Notes.RemoveAll(n => n.OffMeasure == n.Measure && n.OffLocation == n.Location);
        }

        private void BuildMelody(Melody m)
        {
            m_clusterCount += m.Cluster.Length;
            m_totalBeats += m.TotalBeats;

            List<MelodyCluster> smallclusters = m.Cluster.Where(cl => cl.Beats < m_progression.Numerator).ToList();
            List<MelodyCluster> largeclusters = m.Cluster.Where(cl => cl.Beats > m_progression.Numerator).ToList();
            MelodyCluster mcl = new MelodyCluster();

            if (smallclusters.Any() && smallclusters.Select(sc => sc.Beats).Distinct().Count() == 1)
                m_baselength = smallclusters.Select(sc => sc.Beats).First();
            else
            {
                m_baselength = m_progression.Numerator;

                if (m.Cluster.Count() > 1 && m.Cluster.Select(cl => (int)cl.Beats).Sum() == m_baselength)
                {
                    mcl.ClusterID = 1;
                    mcl.Beats = m_progression.Numerator;
                    mcl.Location = m.Cluster[0].Location;
                    mcl.Multiplier = 1;
                    mcl.BeatDictionary = new Dictionary<int, string>();

                    //TODO: Handle resolution better
                    foreach (MelodyCluster mc in m.Cluster)
                        mcl.BeatDictionary.Add((mc.Location - mcl.Location) * 2, mc.Notes);
                }
            }

            if (largeclusters.Any())
                m_largeClusterLength = largeclusters.OrderBy(lc => lc.Beats).Select(lc => lc.Beats).First();

            if (m.Multiplier == 0)
                m.Multiplier = (byte)(m_progression.TotalBeats / m.TotalBeats);

            //TODO:Melody multiplier must update location
            for (int cl_mult = 0; cl_mult < m.Multiplier; cl_mult++)
            {
                if (cl_mult > 0)
                {
                    BuildCluster(mcl, m, cl_mult);
                }
                else
                {
                    if (mcl.BeatDictionary != null)
                        m_clusters.Add(mcl);
                    else
                        m_clusters.AddRange(m.Cluster);
                }
            }
        }

        private void BuildCluster(MelodyCluster mcl, Melody m, int multiplier)
        {
            if (mcl.BeatDictionary != null)
            {
                MelodyCluster cluster = (MelodyCluster)mcl.Clone();
                cluster.Location += (int)(m.TotalBeats / m.Multiplier) * multiplier;
                m_clusters.Add(cluster);
            }
            else
            {
                List<MelodyCluster> clusters = new List<MelodyCluster>();

                foreach (MelodyCluster mc in m.Cluster)
                {
                    MelodyCluster cluster = (MelodyCluster)mc.Clone();
                    cluster.Location += (int)(m.TotalBeats / m.Multiplier) * multiplier;
                    clusters.Add(cluster);
                }

                m_clusters.AddRange(clusters);
            }
        }

        private void FillCluster(MelodyCluster mc)
        {
            int measure = (int)(mc.Location / m_progression.Numerator);
            int interval = 0;
            int location = 0;

            string[] notearray = null;
            int[] clusterbeats;

            ConcreteNote n;

            if (m_oldClusterID != mc.ClusterID)
            {
                if (mc.Beats > m_progression.Numerator)
                    clusterbeats = m_largeClusterBeats;
                else if (mc.Beats == m_baselength)
                    clusterbeats = m_clusterBeats;
                else
                {
                    clusterbeats = new int[m_clusterBeats.Count() * (mc.Beats / m_baselength)];

                    for (int cb = 0; cb < mc.Beats / m_baselength; cb++)
                    {
                        for (int icb = 0; icb < m_clusterBeats.Count(); icb++)
                        {
                            clusterbeats[(cb * m_clusterBeats.Count()) + icb] = (cb * (m_baselength * m_resolution)) + m_clusterBeats[icb];
                        }
                    }
                }
            }
            else
                clusterbeats = m_oldClusterBeats;

            //TODO: Add logic for multiple melody measure calculation
            for (int mult = 0; mult < mc.Multiplier; mult++)
            {
                if (mult > 0)
                    mc.Location = mult * mc.Beats + m_previous;

                foreach (int beatnum in clusterbeats)
                {
                    measure = (int)(mc.Location / m_progression.Numerator);
                    //measure += (int)((mult * mc.Beats) / m_progression.Numerator);

                    if (beatnum >= (m_progression.Numerator * m_resolution))
                    {
                        measure += (int)(beatnum / (m_progression.Numerator * m_resolution));
                        location = ((mc.Location % m_progression.Numerator) * m_resolution) + beatnum - (m_progression.Numerator * m_resolution);
                    }
                    else if (measure > 0)
                    {
                        measure += (int)((mc.Location + (beatnum / m_resolution)) - (measure * m_progression.Numerator))
                            / m_progression.Numerator;
                        location = ((mc.Location % m_progression.Numerator) * m_resolution) + beatnum;
                    }
                    else
                        location = (mc.Location * m_resolution) + beatnum;

                    if (mc.BeatDictionary == null)
                        notearray = mc.Notes.Split(',');
                    else
                        notearray = mc.BeatDictionary.Where(bd => bd.Key <= location).OrderByDescending(bd => bd.Key)
                            .Select(bd => bd.Value).First().Split(',');

                    interval = Convert.ToInt32(notearray[Session.RndGenerator.Next(notearray.Length)]);

                    try
                    {
                        if (interval < 1)
                            n = CircleOfFifths.Notes.Where(no => no.Octave == m_melodyOctave - 1 && no.Interval == interval + 6).First();
                        else if (interval > 7)
                            n = CircleOfFifths.Notes.Where(no => no.Octave == m_melodyOctave + 1 && no.Interval == interval - 8).First();
                        else
                            n = CircleOfFifths.Notes.Where(no => no.Octave == m_melodyOctave && no.Interval == interval - 1).First();
                    }
                    catch (InvalidOperationException)
                    {
                        if (interval < 1)
                            n = CircleOfFifths.Notes.Where(no => no.Octave == m_melodyOctave && no.Interval == interval + 6).First();
                        else
                            n = CircleOfFifths.Notes.Where(no => no.Octave == m_melodyOctave && no.Interval == interval - 8).First();
                    }

                    m_notes.Add((ConcreteNote)m_notefactory.GetNote(location, measure, n, m_noteOnMessageNumber, m_noteOffMessageNumber, this));
                }
            }

            m_oldClusterID = mc.ClusterID;
            m_oldClusterBeats = clusterbeats;
        }

        private void AddRemainingNote(MelodyCluster mc, int measure, int location)
        {
            ConcreteNote n = null;

            string[] notearray = mc.Notes.Split(',');
            int interval = Convert.ToInt32(notearray[Session.RndGenerator.Next(notearray.Length)]);

            if (interval < 1)
                n = CircleOfFifths.Notes.Where(no => no.Octave == m_melodyOctave - 1 && no.Interval == interval + 7).First();
            else if (interval > 7)
                n = CircleOfFifths.Notes.Where(no => no.Octave == m_melodyOctave + 1 && no.Interval == interval - 8).First();
            else
                n = CircleOfFifths.Notes.Where(no => no.Octave == m_melodyOctave && no.Interval == interval - 1).First();

            m_notes.Add((ConcreteNote)m_notefactory.GetNote(location, measure, n, m_noteOnMessageNumber, m_noteOffMessageNumber, this));
        }

        private void Multiply()
        {
            int mult = (int)(m_progression.TotalBeats / m_totalBeats);
            int measurespermelody = (int)(m_totalBeats / m_progression.Numerator);

            ConcreteNote[] originalarray = new ConcreteNote[m_notes.Count];

            m_notes.CopyTo(originalarray);

            for (int i = 1; i < mult; i++)
            {
                foreach (ConcreteNote n in originalarray)
                {
                    m_notes.Add((ConcreteNote)m_notefactory.GetNote(n.Location, (measurespermelody * i) + n.Measure, n, m_noteOnMessageNumber, 
                        m_noteOffMessageNumber, this));
                }
            }
        }

        #endregion
    }
}
